package com.example.holes;

import java.util.ArrayList;
import java.util.Random;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Paint.Align;
import android.widget.Toast;

/*
 * GameEngine handles all the game logic and drawing.
 * It is invoked to Update() and Draw() by the
 * GameThread
 */
public abstract class GameEngine {
	// images and touch rects
	protected Resources res;
	private Bitmap bg_ground;
	private Sky sky;
	private Bitmap[] holeImages = new Bitmap[6*3];
	protected ArrayList<Bitmap> moleImages = new ArrayList<Bitmap>();
	protected Bitmap[] lootImages = new Bitmap[2];
	private ArrayList<Rect> holeTouchRects = new ArrayList<Rect>();
	private Rect menuRect;
	// holes, moles, loot, and ui
	protected Hole[] holes = new Hole[9];
	protected Mole[] moles = new Mole[9];
	protected Loot[] loots = new Loot[9];
	private ArrayList<Mole> molesToRemove = new ArrayList<Mole>();
	private ArrayList<Loot> lootToRemove = new ArrayList<Loot>();
	private Health health;
	private Score score;
	protected Timer timer;
	private CoinCounter coinCounter;
	private EnemyInfo enemyInfo;
	// objective text
	private String objectiveText;
	private Paint text = new Paint();
	private int textLeft, textTop;
	// variables used throughout gameEngine logic
	protected Random rand = new Random();
	protected int freeHoles = 9;
	protected int count = 0;
	protected int molesKilled = 0;
	protected int nextSpawn = Global.UPS, nextSpawnMin = 5, nextSpawnConst = Global.UPS;
	protected boolean timesUp = false, stop = false, completed = false;
	private boolean menu = true, startingGame = false, game = false,  endingGame = false;
	
	public GameEngine(Resources res){
		this.res = res;
		Init();
	}
	
	// Specified in the extending Level
	protected abstract String getObjective();
	protected abstract boolean objectiveComplete();
	protected abstract void InitMolesAndLoot();
	protected abstract Mole pickMole(int holeNum);
	protected abstract boolean checkLoot();
	protected abstract void spawnLoot(int holeNum);
	protected abstract boolean spawnTimer();
	protected abstract void adjustSpawns();
	protected abstract void spawnMole();
	
	public void Init(){
		InitBackground();
		InitMolesAndLoot();
		InitHoles();
		InitUI();
		InitTouchRects();
	}
	
	private void InitBackground(){
		Bitmap image;
		
		// init BG Image and set IMAGE_SCALE params
		bg_ground = Bitmap.createBitmap(BitmapFactory.decodeResource(res, R.drawable.bg_ground));
		Global.IMAGE_SCALE_W = (float)Global.ORIG_WIDTH / bg_ground.getWidth(); // used to scale images back to 800x480
		Global.IMAGE_SCALE_H = (float)Global.ORIG_HEIGHT / bg_ground.getHeight(); // since they have been rescaled by density
		bg_ground = Bitmap.createScaledBitmap(bg_ground, Global.WIDTH, Global.HEIGHT, false);
		
		// init Sky
		image = scaleImage(Bitmap.createBitmap(BitmapFactory.decodeResource(res, R.drawable.sun)));
		sky = new Sky(image);
		
		// init objective text
		text.setColor(Color.RED);
		text.setTextSize(50);
		text.setTextAlign(Align.CENTER);
		textLeft = (int)((float)400 * Global.SCALE_RATIO_W);
		textTop = (int)((float)240 * Global.SCALE_RATIO_H);
		this.objectiveText = getObjective();
	}
	
	private void InitUI(){
		Bitmap image;
		health = new Health();	// init Health
		score = new Score();	// init Score
		// init Timer (Moon)
		image = scaleImage(Bitmap.createBitmap(BitmapFactory.decodeResource(res, R.drawable.moon)));
		timer = new Timer(image);
		// init Coins Counter
		image = scaleImage(Bitmap.createBitmap(BitmapFactory.decodeResource(res, R.drawable.ui_coin)));
		coinCounter = new CoinCounter(GameState.coins, image);
		// init Enemy Info
		image = scaleImage(Bitmap.createBitmap(BitmapFactory.decodeResource(res, R.drawable.enemy_info)));
		enemyInfo = new EnemyInfo(image, moleImages);
		enemyInfo.setEnemy(GameState.level);
	}

	public void InitHoles(){
		Bitmap image;
		// init holes images
		holeImages[0] = scaleImage(Bitmap.createBitmap(BitmapFactory.decodeResource(res, R.drawable.hole)));
		holeImages[1] = scaleImage(Bitmap.createBitmap(BitmapFactory.decodeResource(res, R.drawable.hole_open1)));
		holeImages[2] = scaleImage(Bitmap.createBitmap(BitmapFactory.decodeResource(res, R.drawable.hole_open2)));
		holeImages[3] = scaleImage(Bitmap.createBitmap(BitmapFactory.decodeResource(res, R.drawable.hole_open3)));
		holeImages[4] = scaleImage(Bitmap.createBitmap(BitmapFactory.decodeResource(res, R.drawable.hole_open4)));
		holeImages[5] = scaleImage(Bitmap.createBitmap(BitmapFactory.decodeResource(res, R.drawable.hole_open5)));
		
		// 2nd row: 85% width, 75% height
		for (int i = 6; i < 12; i++){
			image = holeImages[i - 6];
			image = Bitmap.createScaledBitmap(image,(int)(image.getWidth()*0.85), (int)(image.getHeight()*0.75), false);
			holeImages[i] = image;
		}
		// 3rd row: 70% width, 50% height
		for (int i = 12; i < 18; i++){
			image = holeImages[i - 12];
			image = Bitmap.createScaledBitmap(image,(int)(image.getWidth()*0.70), (int)(image.getHeight()*0.50), false);
			holeImages[i] = image;
		}
		
		// create the holes
		holes[0] = new Hole(0, holeImages);
		holes[1] = new Hole(1, holeImages);
		holes[2] = new Hole(2, holeImages);
		holes[3] = new Hole(3, holeImages);
		holes[4] = new Hole(4, holeImages);
		holes[5] = new Hole(5, holeImages);
		holes[6] = new Hole(6, holeImages);
		holes[7] = new Hole(7, holeImages);
		holes[8] = new Hole(8, holeImages);
	}
	
	// initializes the touching rects
	private void InitTouchRects(){
		holeTouchRects.add(scaleRect(new Rect(131,333, 309,480))); // hole 0
		holeTouchRects.add(scaleRect(new Rect(310,333, 487,480))); // hole 1
		holeTouchRects.add(scaleRect(new Rect(488,333, 665,480))); // hole 2
		holeTouchRects.add(scaleRect(new Rect(167,219, 321,332))); // hole 3
		holeTouchRects.add(scaleRect(new Rect(322,219, 476,332))); // hole 4
		holeTouchRects.add(scaleRect(new Rect(477,219, 631,332))); // hole 5
		holeTouchRects.add(scaleRect(new Rect(182,129, 325,218))); // hole 6
		holeTouchRects.add(scaleRect(new Rect(326,129, 469,218))); // hole 7
		holeTouchRects.add(scaleRect(new Rect(470,129, 613,218))); // hole 8
		menuRect = scaleRect(new Rect(631,307, 770,384)); // enemy info forward
	}
	
	// scales images to screen size
	public Bitmap scaleImage(Bitmap image){
		int scaledW = (int)(Global.SCALE_RATIO_W * Global.IMAGE_SCALE_W * (float)image.getWidth());
		int scaledH = (int)(Global.SCALE_RATIO_H * Global.IMAGE_SCALE_H * (float)image.getHeight());
		return Bitmap.createScaledBitmap(image, scaledW, scaledH, false);
	}
	
	// scales rects to screen size
	private Rect scaleRect(Rect rect){
		Rect scaled = new Rect();
		scaled.left = (int)(rect.left * Global.SCALE_RATIO_W);
		scaled.top = (int)(rect.top * Global.SCALE_RATIO_H);
		scaled.right = (int)(rect.right * Global.SCALE_RATIO_W);
		scaled.bottom = (int)(rect.bottom * Global.SCALE_RATIO_H);
		return scaled;
	}

	// =========================
	//        v  UPDATE  v
	// =========================
	public void Update(){
		// STARTING ANIMATION
		if (startingGame == true){
			sky.Update();
			if (sky.startAnimDone()){
				enemyInfo = null;
				startingGame = false;
				game = true;
			}
		}
		if (endingGame == true){
			sky.Update();
			if (sky.endAnimDone()){
				endingGame = false;
				GameState.coins = coinCounter.getCoins(); // write coins to gamestate
				clearGame();
				stop();
			}
		}
		
		// GAMEPLAY
		if (game == true){
			count++;
			
			// check if objective is complete
			// stop spawning when times up
			if (objectiveComplete() == false)
				if (spawnTimer())
					spawnMole();
			
			// THINK OF BETTER WAY TO DO FREEHOLES
			freeHoles = 9;
			// update HOLES
			for (Hole hole : holes){
				hole.Update();
				if (hole.isFull())
					freeHoles--;
			}
			
			// update MOLES
			for (Mole mole : moles){
				if (mole != null){
					mole.Update(); // update
					switch(mole.timeUp()){
						// time NOT up
						case -1:break;
						// time up DAMAGE player
						case 0:	health.damage(mole.getAttack());
								holes[mole.getHole()].close();
								killMole(mole);
								break;
						// was a bomb, NO DAMAGE to player
						case 1:	holes[mole.getHole()].close();
								killMole(mole);
								break;
					}
				}
			}
			
			// update LOOT
			for (Loot loot : loots){
				if (loot != null){
					loot.Update(); // update
					// didn't tap in time, loot disappears
					if (loot.isGone()){
						removeLoot(loot);
					}
				}
			}
			
			// REMOVES any moles and loot queued for removal
			removeMolesAndLoot();
			
			// TIMER advances
			// adjust SPAWN TIMES
			if (timesUp == false){
				timer.tick();
				adjustSpawns();
				if (timer.getTimeUp()) // check if time's up
					timesUp = true;
			}
			
			// NEXT LEVEL timer is up
			// end game once all holes are cleared
			if (objectiveComplete() && freeHoles == 9){
				completed = true;
				endingAnimation();
			}
			
			// EXIT if player is dead
			if (health.isDead()){
				GameState.coins = coinCounter.getCoins(); // write coins to gamestate
				stop();
			}
		}
	}
	// =========================
	//        ^  UPDATE  ^
	// =========================
	
	// get a random empty hole
	public int randomHole(){
		int num = rand.nextInt(9);
		// get random empty hole
		while (holes[num].isFull()){
			num = rand.nextInt(9);
		}
		return num;
	}

	// clean up mole, uses queue to safely remove mole
	public void killMole(Mole mole){
		holes[mole.getHole()].removeMole();
		molesToRemove.add(mole);
	}
	
	public void removeLoot(Loot loot){
		holes[loot.getHole()].removeLoot();
		lootToRemove.add(loot);
	}
	
	// removes all moles and loot at end of update queued for removal
	// removed confined to its own exclusive loops to avoid:
	// java.util.ConcurrentModificationException
	public void removeMolesAndLoot(){
		if (!molesToRemove.isEmpty()){
			for (Mole mole : molesToRemove){
				moles[mole.getHole()] = null;
			}
			molesToRemove.clear();
		}
		
		if (!lootToRemove.isEmpty()){
			for (Loot loot : lootToRemove){
				loots[loot.getHole()] = null;
			}
			lootToRemove.clear();
		}
	}
	
	private void stop(){
		stop = true;
	}
	
	// =========================
	//         v  DRAW  v
	// =========================
	public void Draw(Canvas c){
		// ===  MENU  ===
		if (menu == true){
			// draw bg
			sky.Draw(c);
			c.drawBitmap(bg_ground, 0, 0, null);
			// draw enemy info
			enemyInfo.Draw(c);
		}
		// === START/ENDING ANIMATION ===
		else if (startingGame == true){
			sky.Draw(c);						// 1. sky
			c.drawBitmap(bg_ground, 0, 0, null);// 2. ground
			for (Hole hole : holes){
				hole.Draw(c);					// 3. holes
			}
			c.drawText(objectiveText,
					textLeft, textTop, text);	// 4. objective
			health.Draw(c);						// 5. health
			score.Draw(c);						// 6. score
			coinCounter.Draw(c);				// 7. coin counter
		}
		else if (endingGame == true){
			sky.Draw(c);						// 1. sky
			c.drawBitmap(bg_ground, 0, 0, null);// 2. ground
			for (Hole hole : holes){
				hole.Draw(c);					// 3. holes
			}
			health.Draw(c);						// 4. health
			score.Draw(c);						// 5. score
			coinCounter.Draw(c);				// 6. coin counter
		}
			
		// ===  GAME  ===						// DRAW ORDER--GAME
		else if (game == true){
			// draw bg
			sky.Draw(c);						// 1. sky
			timer.Draw(c); 						// 2. moon
			c.drawBitmap(bg_ground, 0, 0, null);// 3. ground
				
			// draw holes
			for (Hole hole : holes){
				hole.Draw(c);					// 4. holes
			}
				
			// draw moles and loot in backwards hole order
			for (int i = 8; -1 < i; i--){
				if (moles[i] != null)
					moles[i].Draw(c);			// 5. moles
				else if (loots[i] != null)
					loots[i].Draw(c);			// 6. loot
			}
				
			// draw UI
			health.Draw(c);						// 7. health
			score.Draw(c);						// 8. score
			coinCounter.Draw(c);				// 9. coin counter
		}
	}
	// ============================
	//          ^  DRAW  ^
	// ============================

	// ============================
	//       v  USER INPUT  v
	// ============================
	// tests if a hole was touched. then tests if that hole has a mole
	// if yes kills it, if not loses life.
	public void touched(Point point){
		point.set(point.x, point.y);
		// ===  MENU  ===
		if (menu == true){
			// pressed forward button
			if (menuRect.contains(point.x, point.y)){
				startingAnimation();
			}
		}
		// ===  GAME  ===
		else if (game == true){
			for (int i=0; i<9; i++){
				// if point touched is a hole
				if (holeTouchRects.get(i).contains(point.x, point.y))
					holeTouched(holes[i]);
			}
		}
	}
	
	// called when a hole is touched by user
	private void holeTouched(Hole hole){
		// HOLE HAS MOLE: attack mole and see the result
		if (hole.hasMole()){
			Mole mole = hole.getMole();
			int result = mole.attack(GameState.attack);
			
			switch (result){
				// HIT SUCCESSFUL
				case 0: break;
				
				// MOLE KILLED
				case 1: moleKilled(mole, hole);
						break;
				
				// WAS A BOMB--damage and explode
				case 2: health.damage(mole.getAttack());
						killMole(mole);
						hole.close();
						break;
						
				// SPIKES WERE UP
				case 3: health.damage(mole.getAttack());
						break;
			}
		}
		// HOLE HAS LOOT: get loot close hole
		else if (hole.hasLoot()){
			Loot loot = hole.getLoot();
			if (loot.getType() == 0) // LOOT == COIN
				coinCounter.plus(loot.getValue());
			else if (loot.getType() == 1) // LOOT == HEART
				health.plus(loot.getValue());
			removeLoot(hole.getLoot());
		}
		// HOLE HAS NO MOLE/NO LOOT: take away health
		else {
			health.damage(5);
		}
	}
	
	// called by USER INPUT when attacking a mole kills the mole
	private void moleKilled(Mole mole, Hole hole){
		molesKilled++;					// 1. count it
		score.plus(mole.getPoints());	// 2. add points
		killMole(mole);					// 3. kill mole
		if (checkLoot())				// 4a. spawn loot
			spawnLoot(hole.getNum());
		else
			hole.close(); 				// 4b. if no loot close hole
	}
	// ============================
	//       ^  USER INPUT  ^
	// ============================
	
	// clears the game to prepare for next level
	// called when level is completed
	private void clearGame(){
		// clear moles
		for (Mole mole : moles){
			if (mole != null)
				killMole(mole);
		}
		
		// clear loot
		for (Loot loot : loots){
			if (loot != null)
				removeLoot(loot);
		}
		
		removeMolesAndLoot();
		// clear holes
		for (Hole hole : holes){
			hole.reset();
		}
		
		count = 0;
		freeHoles = 9;
	}
	
	// advances from game to next level's menu
	// called when level is completed
	private void nextLevel(){
		GameState.level++;
		if (GameState.level == 5)
			stop();
		enemyInfo.setEnemy(GameState.level);
		timer.reset(); // reset timer
		timesUp = false;
		nextSpawnConst = Global.UPS; // reset spawning constant
		menu = true;
	}
	
	// starts the game from enemy info menu
	private void startingAnimation(){
		menu = false;
		sky.startingAnimation();
		startingGame = true;
	}
	
	// starts ending animation from Update
	// when level is completed
	private void endingAnimation(){
		game = false;
		sky.endingAnimation();
		endingGame = true;
	}
	
	// getter
	public boolean getStop(){ return stop; }
	public boolean completed(){ return completed; }
	public int finalScore(){ return score.getScore(); } // called by GameThread on exit
}
